package cs235.sort;

import java.io.StringWriter;
import java.text.DecimalFormat;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;

public class SortAnalyzer {

    private Random random;
    private Map<SortClasses, Map<Sorts, long[]>> compares;
    private Map<SortClasses, Map<Sorts, double[]>> ratios;
    private Map<SortClasses, Map<Sorts, Double>> error;
    private Map<SortClasses, Map<Sorts, String>> bigO;

    private int[] sizes = {100, 200, 400, 800, 1600 };
    private AnalyzerImpl analyzer;
    

    private static DecimalFormat df = new DecimalFormat("#.###");
    

    public static enum SortClasses {
        MERGE_SORT(new MergeSort()),
        QUICK_SORT(new QuickSort()),
        SELECTION_SORT(new SelectionSort()),
        INSERTION_SORT(new InsertionSort()),

        ;
        private TannersSort sort;

        private SortClasses(TannersSort sort) {
            this.sort = sort;
        }

        public TannersSort getSort() {
            return sort;
        }

        public String toString() {
            return sort.getClass().getSimpleName();
        }
    }

    public static enum Sorts {
        SORTED,
        REVERSED,
        RANDOM,

        ;

        public Integer[] getArray(Integer size) {
            SortAnalyzer sortAnalyzer = new SortAnalyzer();
            Integer[] returnable = {};
            if (this.equals(Sorts.SORTED)) {
                returnable = sortAnalyzer.getSorted(size);
            } else if (this.equals(Sorts.RANDOM)) {
                returnable = sortAnalyzer.getRandom(size);
            } else if (this.equals(Sorts.REVERSED)) {
                returnable = sortAnalyzer.getReverse(size);
            }

            return returnable;
        }
    }

    public SortAnalyzer() {
        random = new Random();
        compares = new HashMap<SortClasses, Map<Sorts, long[]>>();
        ratios = new HashMap<SortClasses, Map<Sorts, double[]>>();
        error = new HashMap<SortClasses, Map<Sorts, Double>>();
        bigO = new HashMap<SortClasses, Map<Sorts, String>>();
        analyzer = new AnalyzerImpl();

    }

    public void calculateRatios() {
        for (SortClasses clazz : EnumSet.allOf(SortClasses.class)) {
            TannersSort sort = clazz.getSort();
            compares.put(clazz, new HashMap<Sorts, long[]>());
            ratios.put(clazz, new HashMap<Sorts, double[]>());
            error.put(clazz, new HashMap<Sorts, Double>());
            bigO.put(clazz, new HashMap<Sorts, String>());

            for (Sorts sorter : EnumSet.allOf(Sorts.class)) {
                long[] temp = new long[sizes.length];

                for (int i = 0; i < sizes.length; i++) {
                    sort.sort(sorter.getArray(sizes[i]));
                    temp[i] = sort.getCompares();
                }

                compares.get(clazz).put(sorter, temp.clone());
                analyzer.analyze(sizes, temp.clone());
                ratios.get(clazz).put(sorter, analyzer.getRatios());
                error.get(clazz).put(sorter, analyzer.getError());
                bigO.get(clazz).put(sorter, analyzer.getBigOh());
            }
        }
        print();
    }

    public Integer[] getRandom(Integer size) {
        Integer[] temp = new Integer[size];
        for (int i = 0; i < size; i++) {
            temp[i] = random.nextInt();
        }
        return temp;
    }

    public Integer[] getSorted(Integer size) {
        Integer[] temp = new Integer[size];
        for (int i = 0; i < size; i++) {
            temp[i] = i;
        }
        return temp;
    }

    public Integer[] getReverse(Integer size) {
        Integer[] temp = new Integer[size];
        for (int i = 0; i < size; i++) {
            temp[size - i - 1] = i;
        }
        return temp;
    }
    
    private void print(){
        Iterator<SortClasses> itr = compares.keySet().iterator();
        while (itr.hasNext()){
            SortClasses clazz = itr.next();
            Iterator<Sorts> itr2 = compares.get(clazz).keySet().iterator();
            while (itr2.hasNext()){
                Sorts order = itr2.next();
                System.out.println(clazz.toString() + " with " + order.toString() + " data:");
                System.out.println("sizes: " + arrayToString(sizes));
                System.out.println("compares: " + arrayToString(compares.get(clazz).get(order)));
                System.out.print("ratios: " + arrayToString(ratios.get(clazz).get(order)));
                System.out.print(" " + bigO.get(clazz).get(order));
                System.out.print(" error: " + df.format(error.get(clazz).get(order)) +"\n\n");
            }
        }
    }
    
    public static String arrayToString(int[] array){
        StringWriter writer = new StringWriter();
        for (int i=0; i<array.length; i++){
            writer.write(new Integer(array[i]).toString());
            writer.write(" ");
        }
        return writer.toString();
    }
    
    public static String arrayToString(double[] array){
        StringWriter writer = new StringWriter();
        for (int i=0; i<array.length; i++){
            writer.write(df.format(array[i]));
            writer.write(" ");
        }
        return writer.toString();
    }
    
    public static String arrayToString(long[] array){
        StringWriter writer = new StringWriter();
        for (int i=0; i<array.length; i++){
            writer.write(new Long(array[i]).toString());
            writer.write(" ");
        }
        return writer.toString();
    }
    
    public static String arrayToString(Object[] array){
        StringWriter writer = new StringWriter();
        for (int i=0; i<array.length; i++){
            writer.write(array[i].toString());
            writer.write(" ");
        }
        return writer.toString();
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        SortAnalyzer sorter = new SortAnalyzer();
        sorter.calculateRatios();
    }

}
